<?php

namespace App\Service\Redis;

use App\Jobs\UserTableQuestionCountCalculateJob;
use App\Models\Qh_Knowledge;
use App\Models\Qh_Question;
use App\Models\Qh_UserCollectQuestion;
use App\Models\TQ_Config;
use App\Models\User;
use Carbon\Carbon;
use DB;
use Illuminate\Database\Eloquent\HigherOrderBuilderProxy;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\HigherOrderCollectionProxy;
use PHPUnit\Exception;
use Psr\SimpleCache\InvalidArgumentException;
use RedisException;

class RedisBaseService
{
    protected int $weekSecond = 604800;       // 7天
    protected $redis;

    protected array $relations = [
        'statistics:userLearnQuestionCount',    // 用户刷题总数
        'statistics:userLearnQuestionOfTodayCount', // 用户今日刷题总数
        'statistics:userWrongQuestionCount',        // 用户刷题错误总题数
        'statistics:userCorrectQuestionCount',      // 用户刷题正确总题数
        'statistics:userCollectQuestionCount',      // 用户收藏总题数
        'statistics:CourseQuestionCount',           // 学科下总题数
        'statistics:Knowledge1QuestionCount',       // 章下总题数
        'statistics:Knowledge2QuestionCount',       // 节下总题数
        'statistics:learnUserCountInCourse',        // 学科下在学用户数
        'statistics:userLearnQuestionCountByCourse',    // 用户刷题总数，根据学科分类
        'statistics:userLearnQuestionCountByKnowledge',// 用户刷题总数，根据章节分类
        'page:home',                                // 首页缓存
        'page:questionLibraryIndex',                // 题库首页缓存
        'page:questionLibraryDetails',              // 题库详情页缓存
        'question:list',                            // 试题列表（数组）
        'question:userLearnQuestionLog',            // 用户的答题记录
        'question:userLearnQuestionLogOfKaoshi',    // 用户的答题记录-考试
        'user:userWrongQuestionData',               // 用户的错题记录
        'user:userCollectQuestionData',             // 用户的收藏记录
        'user:userWrongQuestionIds',                // 用户的错题ids
        'user:userCollectQuestionIds',              // 用户的收藏ids
        'user:wrongQuestionData',
        'user:collectQuestionData',
        'user:hadCourseIds',                        // 用户已拥有的学科Ids
        'config:homePageConfig',
        'config:homePageComponents',
        'rank:userLearnQuestionCountRank',    // 刷题总数排名
        'rank:userLearnQuestionOfTodayCountRank',    // 今日刷题总数排名
        'rank:userKsDaysRank',    // 坚持天数排名
    ];

    public function __construct()
    {
        $this->redis = Redis::client();
    }

    // 同步数据
    public function sync(string $key, $value)
    {

    }

    /**
     * 获取用户已拥有的学科Ids
     * @param $userId
     * @return array|false|\Redis|string
     * @throws RedisException
     */
    public function getUserHadCourseIds($userId) {
        $fullKey = 'user:hadCourseIds';
        $hashKey = 'userId='.$userId;
        $cacheData = $this->redis->hGet($fullKey, $hashKey);
        if ($cacheData) {
            return unserialize($cacheData);
        }
        $val = DB::table('qh_user_has_course')->where('userId', $userId)->pluck('courseId')->toArray();
        $this->redis->hSet($fullKey, $hashKey, serialize($val));
        $this->redis->expire($fullKey,1800);
        return $val;
    }

    /**
     * 更新用户购买的学科ids缓存
     * @param $userId
     * @return void
     * @throws RedisException
     */
    public function updateUserHadCourseIds($userId) {
        $fullKey = 'user:hadCourseIds';
        $hashKey = 'userId='.$userId;
        $val = DB::table('qh_user_has_course')->where('userId', $userId)->pluck('courseId')->toArray();
        $this->redis->hSet($fullKey, $hashKey, serialize($val));
        $this->redis->expire($fullKey,1800);
    }


    /**
     * 获取用户的刷题总数
     * @param $userId
     * @return int
     * @throws RedisException
     */
    public function getUserLearnQuestionCount($userId): int
    {
        $fullKey = 'statistics:userLearnQuestionCount';
        $hashKey = 'userId='.$userId;
        try {
            $value = $this->redis->hGet($fullKey, $hashKey);
            if (!$value || isset($todayCount)) {
                $user = DB::table('user')->find($userId);
                $value = $user ? $user->learnQuestionCount : 0;
                $this->updateUserLearnQuestionCount($userId, $value, $this->getUserLearnQuestionCountOfToday($userId));
            }
            return intval($value);
        }catch (Exception $e) {
            return 0;
        }
    }

    /**
     * 获取用户的今日刷题总数
     * @param $userId
     * @return int
     * @throws RedisException
     */
    public function getUserLearnQuestionCountOfToday($userId): int
    {
        $fullKey = 'statistics:userLearnQuestionOfTodayCount';
        $hashKey = 'userId='.$userId;
        try {
            $todayTimestamp = $this->redis->hGet('statistics:userTodayTimestamp', $hashKey);
            // 缓存中数据已过期
            if (!$todayTimestamp || !Carbon::createFromTimestamp($todayTimestamp)->isToday()) {
                $this->redis->hSet('statistics:userTodayTimestamp', $hashKey, time());
                $todayCount = 0;
            }
            $value = $this->redis->hGet($fullKey, $hashKey);
            // 没有缓存
            if (!$value || isset($todayCount)) {
                $user = DB::table('user')->find($userId);
                $allCount = ($this->redis->hGet('statistics:userLearnQuestionCount', $hashKey))?:($user?$user->learnQuestionCount:0);
                $todayCount = $todayCount ?? ($user ? (
                $user->learnQuestionOfLastTime ? (Carbon::parse($user->learnQuestionOfLastTime)->isToday() ? $user->learnQuestionCountOfToday : 0) : 0
                ) : 0);
                $this->updateUserLearnQuestionCount($userId, $allCount, $todayCount);
                $value = $todayCount;
            }

            return intval($value);
        }catch (Exception $e) {
            return 0;
        }
    }

    /**
     * 用户刷题总数，根据学科分类
     * @param $userId
     * @param $courseId
     * @return array|int|mixed
     * @throws RedisException
     */
    public function getUserLearnQuestionCountByCourse($userId, $courseId) {
        $fullKey = 'statistics:userLearnQuestionCountByCourse';
        $hashKey = 'userId='.$userId;
        $courseListStr = $this->redis->hGet($fullKey, $hashKey);

        $val = 0;
        if ($courseListStr) {
            $coursesStrList = explode(',', $courseListStr);
            $fined = false;
            foreach ($coursesStrList as &$courseStr) {
                $_courseObj = explode('_', $courseStr);
                if ($_courseObj[0] == $courseId) {
                    // 找到匹配项，修改对应的数量
                    $val = !empty($_courseObj[1]) ? $_courseObj[1] : 0;
                    $_courseObj[1] = $val;
                    $courseStr = implode('_', $_courseObj); // 将修改后的数组重新转换为字符串
                    $fined = true;
                }
            }

            // 缓存中没有：存入mysql数据
            if (!$fined) {
                // $dbVal = DB::table('qh_user_learn')->where('courseId', $courseId)->where('userId', $userId)->sum('qCount') ?? 0;
                $dbVal = DB::table('qh_user_learn_question_logs')->where('courseId', $courseId)->where('userId', $userId)->count();

                $coursesStrList[] = $courseId.'_'.$dbVal;
            }
        }else {
            // $dbVal = DB::table('qh_user_learn')->where('courseId', $courseId)->where('userId', $userId)->sum('qCount') ?? 0;
            $dbVal = DB::table('qh_user_learn_question_logs')->where('courseId', $courseId)->where('userId', $userId)->count();
            $coursesStrList[] = $courseId.'_'.$dbVal;
        }
        $courseListStr = implode(',', array_unique($coursesStrList));
        $this->redis->hSet($fullKey, $hashKey, $courseListStr);

        return $val;
    }

    /**
     * 更新用户的学科刷题数
     * @param $userId
     * @param $courseId
     * @param $count        // 记录的总数
     * @return void
     * @throws RedisException
     */
    public function updateUserLearnQuestionCountByCourse($userId, $courseId, $count) {
        $fullKey = 'statistics:userLearnQuestionCountByCourse';
        $hashKey = 'userId='.$userId;
        $courseListStr = $this->redis->hGet($fullKey, $hashKey);
        if ($courseListStr) {
            $coursesStrList = explode(',', $courseListStr);
            $fined = false;
            foreach ($coursesStrList as &$courseStr) {
                $_courseObj = explode('_', $courseStr);
                if ($_courseObj[0] == $courseId) {
                    // 找到匹配项，修改对应的数量
                    $_courseObj[1] = $count;
                    $courseStr = implode('_', $_courseObj); // 将修改后的数组重新转换为字符串
                    $fined = true;
                    break; // 可选：如果只需要修改第一个匹配项，找到后就退出循环
                }
            }
            // 缓存中没有：存入mysql数据
            if (!$fined) {
                // $dbVal = DB::table('qh_user_learn')->where('courseId', $courseId)->where('userId', $userId)->sum('qCount') ?? 0;
                $coursesStrList[] = $courseId.'_'.$count;
            }
        }else {
            // $dbVal = DB::table('qh_user_learn')->where('courseId', $courseId)->where('userId', $userId)->sum('qCount') ?? 0;
            $coursesStrList[] = $courseId.'_'.$count;
        }
        $courseListStr = implode(',', array_unique($coursesStrList));
        $this->redis->hSet($fullKey, $hashKey, $courseListStr);
    }

    /**
     * 用户刷题总数，根据章分类
     * @param $userId
     * @param $knowledgeId
     * @return int
     * @throws RedisException
     */
    public function getUserLearnQuestionCountByKnowledge($userId, $knowledgeId): int
    {
        $fullKey = 'statistics:userLearnQuestionCountByKnowledge';
        $hashKey = 'userId='.$userId;
        $knowledgeListStr = $this->redis->hGet($fullKey, $hashKey);

        $val = 0;
        if ($knowledgeListStr) {
            $knowledgesStrList = explode(',', $knowledgeListStr);

            $fined = false;
            foreach ($knowledgesStrList as &$knowledgeStr) {
                $_knowledgeObj = explode('_', $knowledgeStr);
                if ($_knowledgeObj[0] == $knowledgeId) {
                    // 找到匹配项，修改对应的数量
                    $val = !empty($_knowledgeObj[1]) ? $_knowledgeObj[1] : 0;
                    $_knowledgeObj[1] = $val;
                    $knowledgeStr = implode('_', $_knowledgeObj); // 将修改后的数组重新转换为字符串
                    $fined = true;
                    break; // 可选：如果只需要修改第一个匹配项，找到后就退出循环
                }
            }
            // 缓存中没有：存入mysql数据
            if (!$fined) {
                // 需要一级知识点
                $dbVal = DB::table('qh_user_learn_question_logs')->where('knowledgeId', $knowledgeId)->where('userId', $userId)->count();
                // $dbVal = DB::table('qh_user_learn')->where('knowledgeId', $knowledgeId)->where('userId', $userId)->sum('qCount') ?? 0;
                $knowledgesStrList[] = $knowledgeId.'_'.$dbVal;
            }
        }else {
            $dbVal = DB::table('qh_user_learn_question_logs')->where('knowledgeId', $knowledgeId)->where('userId', $userId)->count();
            // $dbVal = DB::table('qh_user_learn')->where('knowledgeId', $knowledgeId)->where('userId', $userId)->sum('qCount') ?? 0;
            $knowledgesStrList[] = $knowledgeId.'_'.$dbVal;
        }
        $knowledgeListStr = implode(',', array_unique($knowledgesStrList));
        $this->redis->hSet($fullKey, $hashKey, $knowledgeListStr);
        return intval($val);
    }

    /**
     * 更新用户的章刷题数
     * @param $userId
     * @param $knowledgeId
     * @param $count
     * @return void
     * @throws RedisException
     */
    public function updateUserLearnQuestionCountByKnowledge($userId, $knowledgeId, $count) {
        $fullKey = 'statistics:userLearnQuestionCountByKnowledge';
        $hashKey = 'userId='.$userId;
        $knowledgeListStr = $this->redis->hGet($fullKey, $hashKey);
        if ($knowledgeListStr) {
            $knowledgesStrList = explode(',', $knowledgeListStr);
            $fined = false;
            foreach ($knowledgesStrList as &$knowledgeStr) {
                $_knowledgeObj = explode('_', $knowledgeStr);
                if ($_knowledgeObj[0] == $knowledgeId) {
                    // 找到匹配项，修改对应的数量
                    $_knowledgeObj[1] = $count;
                    $knowledgeStr = implode('_', $_knowledgeObj); // 将修改后的数组重新转换为字符串
                    $fined = true;
                    break; // 可选：如果只需要修改第一个匹配项，找到后就退出循环
                }
            }

            // 缓存中没有：存入mysql数据
            if (!$fined) {
                // $dbVal = DB::table('qh_user_learn')->where('knowledgeId', $knowledgeId)->where('userId', $userId)->sum('qCount') ?? 0;
                $knowledgesStrList[] = $knowledgeId.'_'.$count;
            }
        }else {
            // $dbVal = DB::table('qh_user_learn')->where('knowledgeId', $knowledgeId)->where('userId', $userId)->sum('qCount') ?? 0;
            $knowledgesStrList[] = $knowledgeId.'_'.$count;
        }

        $knowledgeListStr = implode(',', array_unique($knowledgesStrList));
        $this->redis->hSet($fullKey, $hashKey, $knowledgeListStr);
    }

    /**
     * 获取页面配置信息
     * @return HigherOrderBuilderProxy|mixed|string|null
     */
    public function getPageConfig($key) {
        $fullKey = $key;
        if (Cache::has($fullKey)) {
            return unserialize(Cache::get($fullKey));
        }

        if ($key == 'config:homePageConfig') {
            $config = TQ_Config::where('key', 'home_config')->first();
            if (!$config) {
                $config = new TQ_Config(['key' => 'home_config', 'value' => json_encode([
                    "headerBackgroundImage" => null,
                    'banner' => [],
                    'components' => []
                ])]);
                $config->save();
            }
        }
        elseif ($key == 'config:kaoshiPageConfig') {
            // todo:入库内容需修改
            $config = TQ_Config::where('key', 'home_config')->first();
            if (!$config) {
                $config = new TQ_Config(['key' => 'home_config', 'value' => json_encode([
                    "headerBackgroundImage" => null,
                    'banner' => [],
                    'components' => []
                ])]);
                $config->save();
            }
        }
        else {
            return null;
        }
        Cache::put($fullKey, serialize($config->value));
        return $config->value;
    }

    /**
     * 获取某个章节下在学用户数量
     * @param $knowledgeId
     * @return int
     * @throws RedisException
     */
    public function getLearnUserCountInKnowledge($knowledgeId): int
    {
        $fullKey = 'statistics:learnUserCountInKnowledge';
        $hashKey = 'knowledgeId='.$knowledgeId;
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            return intval($cache);
        }
        $val = DB::table('qh_user_learn')->where('knowledgeId', $knowledgeId)->count();
        $this->redis->hSet($fullKey, $hashKey, $val);
        return $val;
    }

    /**
     * 获取某个学科下在学用户数量
     * @param $courseId
     * @return int
     * @throws RedisException
     * @throws InvalidArgumentException
     */
    public function getLearnUserCountInCourse($courseId): int
    {
        $fullKey = 'statistics:learnUserCountInCourse';
        $hashKey = 'courseId='.$courseId;
        $cache = $this->redis->hGet($fullKey, $hashKey);
        $updattime = Cache::get('statistics_learnUserCountInCourse_updatetime');
        $updattime = $updattime ? Carbon::createFromTimestamp($updattime) : now()->subMinutes(35);
        if ($cache && $updattime->diffInHours(now()) < 30) {
            return intval($cache);
        }
        $val = DB::table('qh_user_learn')
            ->where('courseId', $courseId)
            ->distinct('userId')
            ->count('userId');
        $this->redis->hSet($fullKey, $hashKey, $val);
        Cache::set('statistics_learnUserCountInCourse_updatetime', time());
        return $val;
    }

    /**
     * 获取用户的统计数据
     * @param $fullKey
     * @param $userId
     * @return false|int|\Redis|string
     * @throws RedisException
     */
    public function getUserStatisticsData($fullKey, $userId)
    {
        $hashKey = 'userId='.$userId;
        if ($fullKey != 'statistics:userLearnQuestionOfTodayCount' && $fullKey != 'statistics:userLearnQuestionCount') {
            $cache = $this->redis->hGet($fullKey, $hashKey);
            if(!$cache) {
                $val = $this->getUserStatisticsDataFromDatabase($hashKey, $userId);
                $this->redis->hSet($fullKey, $hashKey, $val);
            }
            return $cache?:0;
        }elseif ($fullKey == 'statistics:userLearnQuestionCount') {
            return $this->getUserLearnQuestionCount($userId);
        }
        else {
            return $this->getUserLearnQuestionCountOfToday($userId);
        }
    }

    /**
     * 删除用户统计数据缓存
     * @param $fullKey
     * @param $userId
     * @return void
     * @throws RedisException
     */
    public function delUserStatisticsData($fullKey, $userId)
    {
        $hashKey = 'userId='.$userId;
        $this->redis->hDel($fullKey, $hashKey);
    }

    /**
     * 从服务器获取用户的统计数据
     * @param $key
     * @param $userId
     * @param null $default
     * @return array|HigherOrderCollectionProxy|int|mixed|null
     * @throws RedisException
     */
    protected function getUserStatisticsDataFromDatabase($key, $userId, $default=null)
    {
        // 用户刷题总数
        if ($key == 'statistics:userLearnQuestionCount') {
            // return DB::table('qh_user_learn_question_logs')->where('userId', $userId)->count();
            $user = User::find($userId);
            return $user ? $user->learnQuestionCount : 0;
        }
        // 用户今日刷题总数
        elseif ($key == 'statistics:userLearnQuestionOfTodayCount') {
            // $user = User::find($userId);
            $user = DB::table('user')->find($userId);

            return $user ? (
            $user->learnQuestionOfLastTime ? (Carbon::parse($user->learnQuestionOfLastTime)->isToday() ? $user->learnQuestionCountOfToday : 0) : 0
            ) : 0;
        }
        // 用户最后刷题时间
        elseif ($key == 'statistics:userTodayTimestamp') {
            $user = DB::table('user')->find($userId);

            return $user ? (
            $user->learnQuestionOfLastTime ?? null) : null;
        }
        // 用户错题数量
        elseif ($key == 'statistics:userWrongQuestionCount') {
            return $this->getUserWrongOrCollectQuestionCount($userId, 1);
        }
        // 用户正确题数量
        elseif ($key == 'statistics:userCorrectQuestionCount') {
            return DB::table('qh_user_question_logs')->where('userId', $userId)->where('result', 1)->count();
        }
        // 用户收藏题数
        elseif ($key == 'statistics:userCollectQuestionCount') {
            return $this->getUserWrongOrCollectQuestionCount($userId, 2);
        }
        // 默认
        else {
            return $default ?? 0;
        }
    }

    /**
     * 获取用户的答题记录（单题）
     * @param $userId
     * @param $questionId
     * @param int $mode // 1：刷题、2：考试
     * @return array|mixed|null
     * @throws RedisException
     */
    public function getUserLearnQuestionLog($userId, $questionId, int $mode=1) {
        $hashKey = 'userId='.$userId;
        if ($mode == 2) {
            $fullKey = 'question:userLearnQuestionLogOfKaoshi';
            $tableName = 'qh_user_kaoshi_question_logs';
        }else {
            $fullKey = 'question:userLearnQuestionLog';
            $tableName = 'qh_user_question_logs';
        }

        $questionKey = 'questionId:'.$questionId;
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            $cacheData = unserialize($cache)?:[];
            if (array_key_exists($questionKey, $cacheData)) {
                $data = $cacheData[$questionKey];
            }
        }
        if (!isset($data)) {
            $log = DB::table($tableName)->where('userId', $userId)->where('questionId', $questionId)->first();
            if (!$log) {
                return null;
            }
            $data = ['userAnswer' => $log->userAnswer, 'result' => $log->result];
            if (isset($cacheData)) {
                $cacheData[$questionKey] = [
                    'userAnswer' => $log->userAnswer,
                    'result' => $log->result
                ];
                $this->redis->hSet($fullKey, $hashKey, serialize($cacheData));
            }
        }
        return $data;
    }

    /**
     * 更新用户的答题记录（单题）
     * @param $userId
     * @param $question
     * @param $params
     * @param int $mode // 1：刷题、2：考试
     * @return void
     * @throws RedisException
     */
    public function updateUserLearnQuestionLog($userId, $question, $params, int $mode=1) {
        $hashKey = 'userId='.$userId;
        if ($mode == 2) {
            $fullKey = 'question:userLearnQuestionLogOfKaoshi';
        }else {
            $fullKey = 'question:userLearnQuestionLog:userId';
        }

        $questionKey = 'questionId:'.$question->id;
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            $cacheData = unserialize($cache)?:[];
            if (array_key_exists($questionKey, $cacheData)) {
                $cacheData[$questionKey]['userAnswer'] = $params['answer'];
                $cacheData[$questionKey]['result'] = $params['result'];
            }else {
                $cacheData[$questionKey] = [
                    'userAnswer' => $params['answer'],
                    'result' => $params['result']
                ];
            }
        }else {
            $cacheData = [
                $questionKey => [
                    'userAnswer' => $params['answer'],
                    'result' => $params['result']
                ]
            ];
        }

        $this->redis->hSet($fullKey, $hashKey, serialize($cacheData));
    }

    /**
     * 删除用户的答题记录
     * @param $userId
     * @param $questionId
     * @return void
     * @throws RedisException
     */
    public function deleteUserLearnQuestionLog($userId, $questionId) {
        $fullKey = 'question:userLearnQuestionLog';
        $hashKey = 'userId='.$userId;
        $questionKey = 'questionId:'.$questionId->id;
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            $cacheData = unserialize($cache)?:[];
            if (array_key_exists($questionKey, $cacheData)) {
                unset($cacheData[$questionKey]);
                $this->redis->hSet($fullKey, $hashKey, serialize($cacheData));
            }
        }
    }
    public function deleteUserLearnQuestionLogMany($userId, $questionIds) {
        $fullKey = 'question:userLearnQuestionLog';
        $hashKey = 'userId='.$userId;
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            $cacheData = unserialize($cache)?:[];
            foreach ($questionIds as $questionId) {
                $questionKey = 'questionId:'.$questionId;
                if (array_key_exists($questionKey, $cacheData)) {
                    unset($cacheData[$questionKey]);
                }
            }
            $this->redis->hSet($fullKey, $hashKey, serialize($cacheData));
        }
    }

    /**
     * 获取用户收藏的试题ids数组
     * @param $userId
     * @return array|mixed
     * @throws RedisException
     */
    public function getUserCollectQuestionIds($userId) {
        $fullKey = 'user:userCollectQuestionIds';
        $hashKey = 'userId='.$userId;
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            return unserialize($cache);
        }
        $val = Qh_UserCollectQuestion::where('userId', $userId)->pluck('questionId')->toArray();
        $this->redis->hSet($fullKey, $hashKey, serialize($val));
        $this->redis->expire($fullKey,1800);
        return $val;
    }

    /**
     * 删除用户收藏的试题ids缓存
     * @param $userId
     * @return void
     * @throws RedisException
     */
    public function deleteUserCollectQuestion($userId) {
        $fullKey = 'user:userCollectQuestionIds';
        $hashKey = 'userId='.$userId;
        $this->redis->hDel($fullKey, $hashKey);
    }

    /**
     * 获取用户的错题/收藏 题数据
     * @param $userId
     * @param $type 1=错题、2=收藏
     * @return array|mixed
     * @throws RedisException
     */
    public function getUserWrongOrCollectQuestionData($userId, $type)
    {
        $hashKey = 'userId='.$userId;
        // 收藏
        if ($type == 2) {
            $fullKey = 'user:userCollectQuestionData';
            $tableName = 'qh_user_collect_questions2';
        }
        // 错题
        else {
            $fullKey = 'user:userWrongQuestionData';
            $tableName = 'qh_user_wrong_questions';
        }
        $cacheData = $this->redis->hGet($fullKey, $hashKey);
        if ($cacheData) {
            return unserialize($cacheData);
        }

        // 获取有结构的ids
        $ids = $this->getUserWrongOrCollectQuestionStructureData($userId, $type);
        if (!$ids) {
            $val = DB::table($tableName)->where('userId', $userId)->value('data');
            $ids = json_decode($val)?:[];
        }

        $data = formatUserWrongOrCollectQuestion($ids, $userId);

        $this->redis->hSet($fullKey, $hashKey, serialize($data));
        return $data;
    }

    /**
     * 获取用户的错题/收藏题 结构数据
     * @param $userId
     * @param $type 1=错题、2=收藏
     * @return array
     * @throws RedisException
     */
    public function getUserWrongOrCollectQuestionStructureData($userId, $type): array
    {
        $hashKey = 'userId='.$userId;
        // 收藏
        if ($type == 2) {
            $fullKey = 'user:userCollectQuestionIds';
        }
        // 错题
        else {
            $fullKey = 'user:userWrongQuestionIds';
        }

        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            return unserialize($cache);
        }

        if ($type == 2) {
            $qIds = DB::table('qh_user_collect_questions')
                ->where('userId', $userId)
                ->orderByRaw('updated_at desc, id asc')
                ->pluck('questionId')
                ->toArray();
        }
        // 错题
        else {
            $qIds = DB::table('qh_user_question_logs')
                ->where('userId', $userId)
                ->where('result', 2)
                ->orderByDesc('id')
                ->pluck('questionId')
                ->toArray();
        }

        // $data = [
        //     'courseId:1223' => [
        //         'knowledgeId:22' => [
        //             'kId:31223' => [11212,323234],
        //             'kId:8787' => [33121,22312],
        //         ],
        //         'knowledgeId:213' => [
        //             'kId:3244' => [356565,8925],
        //             'kId:33535' => [355223,56656],
        //             'kId:78979712' => [1456464,197979],
        //         ]
        //     ]
        // ];

        $data = [];
        $questions = DB::table('qh_questions')->select(['id', 'courseId', 'knowledgeId'])->whereIn('id', $qIds)->get();
        foreach ($questions as $question) {
            $_courseKey = 'courseId:'.$question->courseId;

            $k = DB::table('qh_knowledge')->select(['id', 'parentId'])->find($question->knowledgeId);
            $knowledgeKey = 'knowledgeId:'.$k->parentId;
            $kKey = 'kId:'.$question->knowledgeId;
            // 已存在
            if (array_key_exists($_courseKey, $data)) {
                // 找章
                // $_knowledgeIndex = array_search($knowledgeKey, $data[$_courseIndex]);
                // 已存在
                if (array_key_exists($knowledgeKey, $data[$_courseKey])) {
                    // 找节
                    // $_kIndex = array_search($kKey, $data[$_courseIndex][$_knowledgeIndex]);
                    // 已存在
                    if (array_key_exists($kKey, $data[$_courseKey][$knowledgeKey])) {
                        // 追加题
                        $data[$_courseKey][$knowledgeKey][$kKey][] = $question->id;
                    }
                    // 不存在
                    else {
                        // 追加节+题
                        $data[$_courseKey][$knowledgeKey][$kKey] = [$question->id];
                    }
                }
                // 不存在
                else{
                    // 追加章
                    $data[$_courseKey][$knowledgeKey] = [
                        $kKey => [$question->id]
                    ];
                }
            }
            // 不存在
            else{
                // 追加
                $data[$_courseKey][$knowledgeKey] = [
                    $kKey => [$question->id]
                ];
            }
        }

        if ($type == 2) {
            $this->saveUserCollectQuestionStructureData($userId, $data);
        }else {
            $this->saveUserWrongQuestionStructureData($userId, $data);
        }

        return $data;
    }

    /**
     * 保存用户的错题结构数据
     * @param $userId
     * @param $data
     * @return void
     * @throws RedisException
     */
    private function saveUserWrongQuestionStructureData($userId, $data)
    {
        $questionCount = 0;
        foreach ($data as $datum) {
            foreach ($datum as $knowledge) {
                foreach ($knowledge as $k) {
                    $questionCount += count($k);
                }
            }
        }

        $cacheData = formatUserWrongOrCollectQuestion($data, $userId);

        // 入库
        DB::table('qh_user_wrong_questions')->updateOrInsert(['userId' => $userId], ['data' => json_encode($data), 'questionCount' => $questionCount, 'updated_at' => Carbon::now()]);
        $this->redis->hSet('user:userWrongQuestionIds', 'userId='.$userId, serialize($data));
        $this->redis->hSet('user:userWrongQuestionData', 'userId='.$userId, serialize($cacheData));
        $this->redis->hSet('statistics:userWrongQuestionCount', 'userId='.$userId, $questionCount);
        $this->redis->expire('user:userWrongQuestionIds',1800);
        $this->redis->expire('user:userWrongQuestionData',1800);
        $this->redis->expire('statistics:userWrongQuestionCount',1800);
    }

    /**
     * 保存用户的收藏题结构数据
     * @param $userId
     * @param $data
     * @return void
     * @throws RedisException
     */
    private function saveUserCollectQuestionStructureData($userId, $data)
    {
        $questionCount = 0;
        foreach ($data as $datum) {
            foreach ($datum as $knowledge) {
                foreach ($knowledge as $k) {
                    $questionCount += count($k);
                }
            }
        }

        $cacheData = formatUserWrongOrCollectQuestion($data, $userId);

        // 入库
        DB::table('qh_user_collect_questions2')->updateOrInsert(['userId' => $userId], ['data' => json_encode($data), 'questionCount' => $questionCount, 'updated_at' => Carbon::now()]);
        $this->redis->hSet('user:userCollectQuestionIds', 'userId='.$userId, serialize($data));
        $this->redis->hSet('user:userCollectQuestionData', 'userId='.$userId, serialize($cacheData));
        $this->redis->hSet('statistics:userCollectQuestionCount', 'userId='.$userId, $questionCount);
        $this->redis->expire('user:userCollectQuestionIds',1800);
        $this->redis->expire('user:userCollectQuestionData',1800);
        $this->redis->expire('statistics:userCollectQuestionCount',1800);
    }

    /**
     * 删除某一错题数据
     * @param $userId
     * @param $key
     * @param $mode
     * @param $type 1=错题、2=收藏
     * @return void
     * @throws RedisException
     */
    public function deleteUserWrongOrCollectQuestionData($userId, $key, $mode, $type) {
        // 获取存储的结构数据
        $userQuestionStructureData = $this->getUserWrongOrCollectQuestionStructureData($userId, $type);

        if (!empty($userQuestionStructureData)) {
            // 单题模式
            if ($mode == 1) {
                foreach ($userQuestionStructureData as &$knowledgeArr) {
                    foreach ($knowledgeArr as &$knowledge) {
                        foreach ($knowledge as $_k => &$questions) {
                            if (in_array($key, $questions)) {
                                unset($questions[array_search($key, $questions)]);
                            }
                        }
                    }
                }
                unset($knowledge, $questions); // 释放引用
            }
            // 学科模式
            elseif ($mode == 2) {
                unset($userQuestionStructureData['courseId:'.$key]);
            }
            // 章模式
            elseif ($mode == 3) {
                foreach ($userQuestionStructureData as $_c => &$knowledgeArr) {
                    if (array_key_exists('knowledgeId:'.$key, $knowledgeArr)) {
                        unset($knowledgeArr['knowledgeId:'.$key]);
                    }
                    // 如果学科为空
                    if (count($knowledgeArr) == 0) {
                        unset($userQuestionStructureData[$_c]);
                    }
                }
                unset($knowledgeArr); // 释放引用
            }
            // 节模式
            elseif ($mode == 4) {
                foreach ($userQuestionStructureData as $_c => &$knowledgeArr) {
                    foreach ($knowledgeArr as $_k => &$knowledge) {
                        if (array_key_exists('kId:'.$key, $knowledge)) {
                            unset($knowledge['kId:'.$key]);
                        }
                        // 如果章为空
                        if (count($knowledge) == 0) {
                            unset($knowledgeArr[$_k]);
                        }
                        // 如果学科为空
                        if (count($knowledgeArr) == 0) {
                            unset($userQuestionStructureData[$_c]);
                        }
                    }
                }
                unset($knowledge, $knowledgeArr); // 释放引用
            }
        }

        // 更新
        if ($type == 2) {
            $this->saveUserCollectQuestionStructureData($userId, $userQuestionStructureData);
            // 删除redis中缓存
            $this->redis->hDel('user:userCollectQuestionData', 'userId='.$userId);
        }else {
            $this->saveUserWrongQuestionStructureData($userId, $userQuestionStructureData);
            // 删除redis中缓存
            $this->redis->hDel('user:userWrongQuestionData', 'userId='.$userId);
        }
    }

    /**
     * 获取用户错题/收藏题数量
     * @param $userId
     * @param $type 1=错题、2=收藏
     * @return array|int|mixed
     * @throws RedisException
     */
    public function getUserWrongOrCollectQuestionCount($userId, $type)
    {
        $hashKey = 'userId='.$userId;
        if ($type == 2) {
            $fullKey = 'statistics:userCollectQuestionCount';
            $tableName = 'qh_user_collect_questions2';
        }else {
            $fullKey = 'statistics:userWrongQuestionCount';
            $tableName = 'qh_user_wrong_questions';
        }

        // 先查redis
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            return $cache;
        }
        // 查mysql
        $count = DB::table($tableName)->where('userId', $userId)->value('questionCount');
        if ($count) {
            return $count;
        }

        // 重新统计
        $this->getUserWrongOrCollectQuestionStructureData($userId, $type);

        // 重新查redis
        $cache = $this->redis->hGet($fullKey, $hashKey);
        if ($cache) {
            return $cache;
        }else {
            return 0;
        }
    }

    /**
     * 更新用户刷题总数
     * @throws RedisException
     */
    public function updateUserLearnQuestionCount($userId, $allCount, $todayCount) {
        $todayTimestamp = $this->redis->hGet('statistics:userTodayTimestamp', 'userId='.$userId);
        if (!$todayTimestamp || !Carbon::createFromTimestamp($todayTimestamp)->isToday()) {
            $this->redis->hSet('statistics:userTodayTimestamp', 'userId='.$userId, time());
            $todayCount = $todayCount==0?0:1;
        }

        $hashKey = 'userId='.$userId;
        $this->redis->hSet('statistics:userLearnQuestionCount', $hashKey, $allCount);
        $this->redis->hSet('statistics:userLearnQuestionOfTodayCount', $hashKey, $todayCount);
        $this->redis->expire('statistics:userLearnQuestionCount', $this->weekSecond);
        $this->redis->expire('statistics:userLearnQuestionOfTodayCount', Carbon::now()->diffInSeconds(Carbon::now()->endOfDay()));

        // 入库
        // DB::table('user')->where('id', $userId)->update([
        //     'learnQuestionCount' => $allCount,
        //     'learnQuestionCountOfToday' => $todayCount,
        //     'learnQuestionOfLastTime' => Carbon::now(),
        // ]);
    }

    /**
     * 用户错题入库
     * @param $userId
     * @param Qh_Question $question
     * @param $type 1=错题、2=收藏
     * @return void
     * @throws RedisException
     */
    public function pushWrongOrCollectQuestionToDb($userId, Qh_Question $question, $type) {
        $userQuestionStructureData = $this->getUserWrongOrCollectQuestionStructureData($userId, $type);
        $courseKey = 'courseId:'.$question->courseId;
        $k = Qh_Knowledge::find($question->knowledgeId);
        $knowledgeKey = 'knowledgeId:'.$k->parentId;
        $kKey = 'kId:'.$question->knowledgeId;
        if (array_key_exists($courseKey, $userQuestionStructureData)) {
            if (array_key_exists($knowledgeKey, $userQuestionStructureData[$courseKey])) {
                if (array_key_exists($kKey, $userQuestionStructureData[$courseKey][$knowledgeKey])) {
                    if (!in_array($question->id, $userQuestionStructureData[$courseKey][$knowledgeKey][$kKey])) {
                        $userQuestionStructureData[$courseKey][$knowledgeKey][$kKey][] = $question->id;
                    }
                }else {
                    $userQuestionStructureData[$courseKey][$knowledgeKey][$kKey] = [$question->id];
                }
            }else {
                $userQuestionStructureData[$courseKey][$knowledgeKey] = [
                    $kKey => [$question->id]
                ];
            }
        }else {
            $userQuestionStructureData[$courseKey] = [
                $knowledgeKey => [
                    $kKey => [$question->id]
                ]
            ];
        }
        if ($type == 2) {
            // 保存
            $this->saveUserCollectQuestionStructureData($userId, $userQuestionStructureData);
        }else {
            // 保存
            $this->saveUserWrongQuestionStructureData($userId, $userQuestionStructureData);
        }
    }

    /**
     * 用户错题出库
     * @param $userId
     * @param Qh_Question $question
     * @param $type
     * @return void
     * @throws RedisException
     */
    public function popWrongOrCollectQuestion($userId, Qh_Question $question, $type) {
        $userQuestionStructureData = $this->getUserWrongOrCollectQuestionStructureData($userId, $type);
        $courseKey = 'courseId:'.$question->courseId;
        $k = Qh_Knowledge::find($question->knowledgeId);
        $knowledgeKey = 'knowledgeId:'.$k->parentId;
        $kKey = 'kId:'.$question->knowledgeId;

        // \Log::debug('错题OR收藏题出库处理开始:'.$question->id, $userQuestionStructureData);

        if (array_key_exists($courseKey, $userQuestionStructureData)) {
            if (array_key_exists($knowledgeKey, $userQuestionStructureData[$courseKey])) {
                if (array_key_exists($kKey, $userQuestionStructureData[$courseKey][$knowledgeKey])) {
                    $qIdx = array_search($question->id, $userQuestionStructureData[$courseKey][$knowledgeKey][$kKey]);
                    if ($qIdx !== false) {
                        // 使用 array_splice 替代 unset，保持原始数组结构
                        array_splice($userQuestionStructureData[$courseKey][$knowledgeKey][$kKey], $qIdx, 1);
                    }
                    // 如果该子数组已经为空，则从父数组中移除该子数组
                    if (empty($userQuestionStructureData[$courseKey][$knowledgeKey][$kKey])) {
                        unset($userQuestionStructureData[$courseKey][$knowledgeKey][$kKey]);
                    }
                    // 如果父数组也为空，则从父数组中移除该子数组
                    if (empty($userQuestionStructureData[$courseKey][$knowledgeKey])) {
                        unset($userQuestionStructureData[$courseKey][$knowledgeKey]);
                    }
                    // 如果父数组也为空，则从根数组中移除该子数组
                    if (empty($userQuestionStructureData[$courseKey])) {
                        unset($userQuestionStructureData[$courseKey]);
                    }
                }
            }
        }

        // \Log::debug('错题OR收藏题出库处理结束:'.$question->id, $userQuestionStructureData);
        // \Log::debug('错题OR收藏题出库处理结束:'.$question->id);

        // 保存新的数据
        if ($type == 2) {
            $this->saveUserCollectQuestionStructureData($userId, $userQuestionStructureData);
        }else {
            $this->saveUserWrongQuestionStructureData($userId, $userQuestionStructureData);
        }
    }

    /**
     * 获取用户刷题数量排名结果
     */
    public function getUserLearnQuestionCountRank() {
        // 获取所有的刷题记录
        return unserialize(Cache::get('rank:userLearnQuestionCountRank'));
    }

    /**
     * 获取用户今日刷题数量排名结果
     */
    public function getUserLearnQuestionOfTodayCountRank() {
        return unserialize(Cache::get('rank:userLearnQuestionOfTodayCountRank'));
    }

    /**
     * 获取坚持天数排名
     */
    public function getUserKsDayRank() {
        return unserialize(Cache::get('rank:userKsDaysRank'));
    }

    /**
     * 获取用户答题正确率
     * @param $userId
     * @return float|int
     * @throws RedisException
     */
    public function getUserQuestionRate($userId) {
        $wrongQuestionCount = null;
        $correctQuestionCount = null;
        //$wrongQuestionCount = $this->getUserWrongOrCollectQuestionCount($userId, 1);
        //$correctQuestionCount = $this->getUserStatisticsData('statistics:userCorrectQuestionCount', $userId);
        if (!$wrongQuestionCount || !$correctQuestionCount) {
            $user = DB::table('user')->select(['id', 'wrongQuestionCount', 'correctQuestionCount'])->where('id', $userId)->first();
            if ($user) {
                if ($user->wrongQuestionCount == 0) {
                    UserTableQuestionCountCalculateJob::dispatch($userId, 2);
                }
                if ($user->correctQuestionCount == 0) {
                    UserTableQuestionCountCalculateJob::dispatch($userId, 1);
                }
                if (!$wrongQuestionCount) {
                    $wrongQuestionCount = $user->wrongQuestionCount;
                    $this->redis->hSet('statistics:userWrongQuestionCount', 'userId='.$userId, $wrongQuestionCount);
                }
                if (!$correctQuestionCount) {
                    $correctQuestionCount = $user->correctQuestionCount;
                    $this->redis->hSet('statistics:userCorrectQuestionCount', 'userId='.$userId, $correctQuestionCount);
                }
            }
        }

        $rate = 0;
        $sum = $correctQuestionCount + $wrongQuestionCount;
        if ($sum > 0) {
            $rate = round($correctQuestionCount / $sum, 2);
        }
        return $rate;
    }

    /**
     * 获取用户今日答题正确率
     * @param $userId
     * @return float|int
     * @throws RedisException
     */
    public function getUserQuestionRateOfToday($userId) {
        $rate = 0;
        $user = DB::table('user')->select(['id', 'wrongQuestionCountOfToday', 'correctQuestionCountOfToday', 'learnQuestionOfLastTime'])->where('id', $userId)->first();
        if ($user) {
            if ($user->learnQuestionOfLastTime != null && Carbon::make($user->learnQuestionOfLastTime)->isToday()) {
                $this->redis->hSet('statistics:userWrongQuestionCountOfToday', 'userId='.$userId, $user->wrongQuestionCountOfToday);
                $this->redis->hSet('statistics:userCorrectQuestionCountOfToday', 'userId='.$userId, $user->correctQuestionCountOfToday);
            }else {
                DB::table('user')->where('id', $userId)->update([
                    'learnQuestionOfLastTime' => now(),
                    'learnQuestionCountOfToday' => 0,
                    'wrongQuestionCountOfToday' => 0,
                    'correctQuestionCountOfToday' => 0,
                ]);

                $this->redis->hSet('statistics:userLearnQuestionOfTodayCount', 'userId='.$userId, 0);
                $this->redis->hSet('statistics:userWrongQuestionCountOfToday', 'userId='.$userId, 0);
                $this->redis->hSet('statistics:userCorrectQuestionCountOfToday', 'userId='.$userId, 0);
            }
            $sum = $user->correctQuestionCountOfToday + $user->wrongQuestionCountOfToday;
            if ($sum > 0 && $user->correctQuestionCountOfToday > 0) {
                $rate = round($user->correctQuestionCountOfToday / $sum, 2);
            }
        }

        return $rate;
    }

    /**
     * 删除用户缓存的错题和收藏
     * @param $userId
     * @return void
     * @throws RedisException
     */
    public function deleteUserWrongOrCollectQuestionAllCache($userId) {
        $hashKey = 'userId='.$userId;
        $this->redis->hDel('user:userWrongQuestionIds', $hashKey);
        $this->redis->hDel('user:userWrongQuestionData', $hashKey);
        $this->redis->hDel('user:userCollectQuestionIds', $hashKey);
        $this->redis->hDel('user:userCollectQuestionData', $hashKey);
    }
}
